#include "objects.h"

using rtMath::vec2;
using rtMath::vec3;
using rtMath::vec4;


Material::Material()
	: m_SpecularLevel(0.0f)
	, m_Glossiness(0.0f)
	, m_Transparency(0.0f)
	, m_Reflectivity(0.0f)
	, m_RefractionIndex(1.0f)
	, m_AlbedoTexture(-1)
	, m_BumpTexture(-1)
{
}
Material::~Material()
{
}

void Material::SetName(const rtString& name)
{
	m_Name = name;
}

const rtString& Material::GetName(void) const
{
	return m_Name;
}

void Material::SetDiffuse(const vec3& diffuse)
{
	m_Diffuse = diffuse;
}

const rtMath::vec3& Material::GetDiffuse(void) const
{
	return m_Diffuse;
}

void Material::SetSpecular(const rtMath::vec3& specular)
{
	m_Specular = specular;
}

const rtMath::vec3& Material::GetSpecular(void) const
{
	return m_Specular;
}

void Material::SetSpecularLevel(float specLevel)
{
	m_SpecularLevel = specLevel;
}

float Material::GetSpecularLevel(void) const
{
	return m_SpecularLevel;
}

void Material::SetGlossiness(float glossiness)
{
	m_Glossiness = glossiness;
}

float Material::GetGlossiness(void) const
{
	return m_Glossiness;
}

void Material::SetTransparency(float transparency)
{
	m_Transparency = transparency;
}

float Material::GetTransparency(void) const
{
	return m_Transparency;
}

void Material::SetReflectivity(float reflectivity)
{
	m_Reflectivity = reflectivity;
}

float Material::GetReflectivity(void) const
{
	return m_Reflectivity;
}

void Material::SetRefractionIndex(float reflraction)
{
	m_RefractionIndex = reflraction;
}

float Material::GetRefractionIndex(void) const
{
	return m_RefractionIndex;
}

void Material::SetAlbedoTexture(int albedoTex)
{
	m_AlbedoTexture = albedoTex;
}

int Material::GetAlbedoTexture(void) const
{
	return m_AlbedoTexture;
}

void Material::SetBumpTexture(int bumpTex)
{
	m_BumpTexture = bumpTex;
}

int Material::GetBumpTexture(void) const
{
	return m_BumpTexture;
}


ShapeSphere::ShapeSphere()
	: SceneShape(SceneShape::ShapeSphere)
	, m_Radius(1.0f)
{
}
ShapeSphere::~ShapeSphere()
{
}


bool ShapeSphere::RayCollisionCheck(const rtMath::Ray& ray, RayHitInfo* hitInfo /*= NULL*/, float t /*= rtMath::MAX_FLOAT*/) const
{
	vec3 dist = this->GetPosition() - ray.origin;
	float B = rtMath::dot(ray.dir, dist);
	float D = B * B - rtMath::dot(dist, dist) + this->GetRadius() * this->GetRadius();
	if (D < 0.0f)
		return false;

	const float sqrtD = sqrtf(D);
	float t0 = B - sqrtD;
	float t1 = B + sqrtD;

	bool retvalue = false;
	if ((t0 > 0.1f) && (t0 < t)) 
	{
		t = t0;
		retvalue = true;
	}
	if ((t1 > 0.1f) && (t1 < t)) 
	{
		t = t1;
		retvalue = true;
	}

	if (retvalue && hitInfo)
	{
		hitInfo->distToHit = t;
		hitInfo->hitPos = ray.origin + (ray.dir * t);
		hitInfo->hitNormal = hitInfo->hitPos - this->GetPosition();
		hitInfo->hitNormal.normalize();
		hitInfo->hitTexcoord = this->CalculateTexcoords(hitInfo->hitPos);
	}

	return retvalue;
}

void ShapeSphere::SetRadius(float radius)
{
	m_Radius = radius;
}

float ShapeSphere::GetRadius(void) const
{
	return m_Radius;
}

vec2 ShapeSphere::CalculateTexcoords(const vec3& pos) const
{
	// iOrange - vectors for texture mapping
	static const vec3 Vn = vec3(0.0f, 1.0f, 0.0f);
	static const vec3 Ve = vec3(1.0f, 0.0f, 0.0f);
	static const vec3 Vc = rtMath::cross(Vn, Ve);

	vec3 vp = (pos - this->GetPosition()) / m_Radius;
	float phi = acosf(-rtMath::dot(vp, Vn));
	float u, v = phi * (1.0f / rtMath::PI);
	float theta = (acosf(rtMath::dot(Ve, vp) / sinf(phi))) * (2.0f / rtMath::PI);
	if (rtMath::dot(Vc, vp) >= 0.0f)
		u = 1.0f - theta;
	else
		u = theta;

	return vec2(u, v);
}



ShapeBox::ShapeBox()
	: SceneShape(SceneShape::ShapeBox)
{
}
ShapeBox::~ShapeBox()
{
}

bool ShapeBox::RayCollisionCheck(const rtMath::Ray& ray, RayHitInfo* hitInfo /*= NULL*/, float t /*= rtMath::MAX_FLOAT*/) const
{
	float tmin, tmax, tymin, tymax, tzmin, tzmax;
	int normalAxis = 0,
		normalAxisMin = 0,
		normalAxisMax = 0;

	tmin  = (m_MinMax[    ray.posneg[0]].x - ray.origin.x) * ray.dir_inv.x;
	tmax  = (m_MinMax[1 - ray.posneg[0]].x - ray.origin.x) * ray.dir_inv.x;
	tymin = (m_MinMax[    ray.posneg[1]].y - ray.origin.y) * ray.dir_inv.y;
	tymax = (m_MinMax[1 - ray.posneg[1]].y - ray.origin.y) * ray.dir_inv.y;

	if ((tmin > tymax) || (tymin > tmax))
		return false;

	if (tymin > tmin)
	{
		tmin = tymin;
		normalAxisMin = 1;
	}
	if (tymax < tmax)
	{
		tmax = tymax;
		normalAxisMax = 1;
	}

	tzmin = (m_MinMax[    ray.posneg[2]].z - ray.origin.z) * ray.dir_inv.z;
	tzmax = (m_MinMax[1 - ray.posneg[2]].z - ray.origin.z) * ray.dir_inv.z;

	if ((tmin > tzmax) || (tzmin > tmax))
		return false;

	if (tzmin > tmin)
	{
		tmin = tzmin;
		normalAxisMin = 2;
	}
	if (tzmax < tmax)
	{
		tmax = tzmax;
		normalAxisMax = 2;
	}

	bool result = false;
	if ((tmin > 0.1f) && (tmin < t))
	{
		t = tmin;
		result = true;
		normalAxis = normalAxisMin;
	}
	else if ((tmax > 0.1f) && (tmax < t))
	{
		t = tmax;
		result = true;
		normalAxis = normalAxisMax;
	}

	if (result && hitInfo)
	{
		hitInfo->distToHit = t;
		hitInfo->hitPos = ray.origin + (ray.dir * t);
		hitInfo->hitNormal = vec3(0, 0, 0);
		vec3 dir = hitInfo->hitPos - this->GetPosition();
		hitInfo->hitNormal[normalAxis] = rtMath::sign(dir[normalAxis]);
		hitInfo->hitTexcoord = this->CalculateTexcoords(hitInfo->hitPos, normalAxis);
	}

	return result;
}

void ShapeBox::SetMinimum(const vec3& minimum)
{
	m_MinMax[0] = minimum;
}

const vec3& ShapeBox::GetMinimum(void) const
{
	return m_MinMax[0];
}

void ShapeBox::SetMaximum(const vec3& maximum)
{
	m_MinMax[1] = maximum;
}

const vec3& ShapeBox::GetMaximum(void) const
{
	return m_MinMax[1];
}

vec2 ShapeBox::CalculateTexcoords(const vec3& pos, int normAxis) const
{
	float posU, posV;
	vec2 minU, maxU;
	if (0 == normAxis)
	{
		minU.x = GetMinimum().z;
		minU.y = GetMinimum().y;
		maxU.x = GetMaximum().z;
		maxU.y = GetMaximum().y;

		posU = pos.z - minU.x;
		posV = pos.y - minU.y;
	}
	else if (1 == normAxis)
	{
		minU.x = GetMinimum().x;
		minU.y = GetMinimum().z;
		maxU.x = GetMaximum().x;
		maxU.y = GetMaximum().z;

		posU = pos.x - minU.x;
		posV = pos.z - minU.y;
	}
	else if (2 == normAxis)
	{
		minU.x = GetMinimum().x;
		minU.y = GetMinimum().y;
		maxU.x = GetMaximum().x;
		maxU.y = GetMaximum().y;

		posU = pos.x - minU.x;
		posV = pos.y - minU.y;
	}

	vec2 diff = maxU - minU;
	return vec2(posU / diff.x, 1.0f- (posV / diff.y));
}




ShapePlane::ShapePlane()
	: SceneShape(SceneShape::ShapePlane)
	, m_Distance(0.0f)
{
}
ShapePlane::~ShapePlane()
{
}

bool ShapePlane::RayCollisionCheck(const rtMath::Ray& ray, RayHitInfo* hitInfo /*= NULL*/, float t /*= rtMath::MAX_FLOAT*/) const
{
	bool result = false;

	float k0 = rtMath::dot(m_Normal, ray.dir);
	if (k0 < 0.0f)
	{
		float k1 = rtMath::dot(vec4(m_Normal, m_Distance), vec4(ray.origin, 1.0f));
		float dist = -k1 / k0;
		if (k1 > 0.0f && dist < t)
		{
			result = true;
			if (hitInfo)
			{
				hitInfo->distToHit = dist;
				hitInfo->hitPos = ray.origin + (ray.dir * dist);
				hitInfo->hitNormal = m_Normal;
				hitInfo->hitTexcoord = vec2(rtMath::dot(hitInfo->hitPos, m_UAxis) * 0.015625f,
											rtMath::dot(hitInfo->hitPos, m_VAxis) * 0.015625f);
			}
		}
	}

	return result;
}

void ShapePlane::SetNormal(const vec3& normal)
{
	m_Normal = normal;
}

const vec3& ShapePlane::GetNormal(void) const
{
	return m_Normal;
}

void ShapePlane::SetDistance(float distance)
{
	m_Distance = distance;
}

float ShapePlane::GetDistance(void) const
{
	return m_Distance;
}

void ShapePlane::Setup(void)
{
	m_UAxis = vec3(m_Normal.y, m_Normal.z, -m_Normal.x);
	m_VAxis = rtMath::cross(m_UAxis, m_Normal);
}

const vec3& ShapePlane::GetUAxis(void) const
{
	return m_UAxis;
}

const vec3& ShapePlane::GetVAxis(void) const
{
	return m_VAxis;
}




SceneLight::SceneLight()
	: SceneObject(SceneObject::ObjectTypeLight)
{
}
SceneLight::~SceneLight()
{
}

void SceneLight::SetIntensity(const rtMath::vec3& intensity)
{
	m_Intensity = intensity;
}

const rtMath::vec3& SceneLight::GetIntensity(void) const
{
	return m_Intensity;
}



SceneCamera::SceneCamera()
	: SceneObject(SceneObject::ObjectTypeCamera)
	, m_Direction(0, 0, -1)
	, m_UpVector(0, 1, 0)
	, m_FOV(45.0f)
{
}
SceneCamera::~SceneCamera()
{
}


void SceneCamera::SetDirection(const vec3& dir)
{
	m_Direction = dir;
}

const vec3& SceneCamera::GetDirection(void) const
{
	return m_Direction;
}

void SceneCamera::SetUpVector(const vec3& dir)
{
	m_UpVector = dir;
}

const vec3& SceneCamera::GetUpVector(void) const
{
	return m_UpVector;
}

void SceneCamera::SetFOV(float fov)
{
	m_FOV = fov;
}

float SceneCamera::GetFOV(void) const
{
	return m_FOV;
}

void SceneCamera::SetNearClip(float nearClip)
{
	m_NearClip = nearClip;
}

float SceneCamera::GetNearClip(void) const
{
	return m_NearClip;
}

void SceneCamera::SetFarClip(float farClip)
{
	m_FarClip = farClip;
}

float SceneCamera::GetFarClip(void) const
{
	return m_FarClip;
}

void SceneCamera::Setup(float width, float height)
{
	vec3 u = rtMath::cross(m_UpVector, m_Direction);
	vec3 v = rtMath::cross(m_Direction, u);

	float aspectRatio = width / height;
	float planeWidth = tanf((m_FOV * rtMath::DEG2RAD) * 0.5f);

	m_U = u * (planeWidth * aspectRatio) / u.length();
	m_V = v * (planeWidth) / v.length();
}

rtMath::Ray SceneCamera::ComputeRay(float x, float y, float width, float height) const
{
	vec3 rayDir = m_Direction + (m_U * ((x / (width - 1.0f)) * 2.0f - 1.0f)) - (m_V * ((y / (height - 1.0f)) * 2.0f - 1.0f));

	return rtMath::Ray(this->GetPosition(), rayDir);
}
